﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using StockFinder.Services.Extractors;
using StockFinder.Model;
using StockFinder.Model.Constants;
using Db4objects.Db4o;
using Db4objects.Db4o.Config;
using StockFinder.Model.Enumerations;
using StockFinder.Services.Extractors.Yahoo;
using log4net;
using Db4objects.Db4o.TA;
using Db4objects.Db4o.Defragment;

namespace StockFinder.Services.Engines
{
    /// <summary>
    /// Updates the price history database with latest symbols and prices
    /// </summary>
    public class PriceHistoryContainerUpdater
    {
        private static readonly ILog _Log = LogManager.GetLogger(typeof(PriceHistoryContainerUpdater));   

        public void Run()
        {            
            #region 1. get exchange symbol extrators

            _Log.InfoFormat("Getting exchange symbols");

            IEnumerable<ISymbolListExtractor> extractors = ExchangeSymbolsExtratorContainer.GetExtractors();

            List<Symbol> downloadedSymbols = new List<Symbol>();

            foreach (var extractor in extractors)
            {
                //1 a. get symbols from each exchange
                _Log.InfoFormat("Downloading symbols");

                downloadedSymbols.AddRange(extractor.GetSymbolList());                
            }

            _Log.InfoFormat("Downloaded {0} symbols", downloadedSymbols.Count);

            #endregion

            #region Generate new PriceHistoryContainer if it doesnt exist

            using (IObjectContainer database = Db4oEmbedded.OpenFile(GeCreateIEmbeddedConfiguration(),
                ApplicationConfiguation.ObjectDatabaseSRootDirectory + ObjectDatabaseFilenames.PRICE_HISTORY))
            {
                PriceHistoryContainer priceHistoryContainer = null;

                //get or create PriceHistoryContainer
                List<PriceHistoryContainer> priceHistoryContainers = database.Query<PriceHistoryContainer>().ToList();

                //PriceHistoryContainer doesnt exist so create it
                if (priceHistoryContainers == null || priceHistoryContainers.Count() == 0)
                {
                    priceHistoryContainer = new PriceHistoryContainer();
                    database.Store(priceHistoryContainer);
                }
            }

            #endregion

            #region Update PriceHistoryContainer Symbol List

            _Log.InfoFormat("Loading PriceHistoryContainer");            

            using (IObjectContainer database = Db4oEmbedded.OpenFile(GeCreateIEmbeddedConfiguration(),
                ApplicationConfiguation.ObjectDatabaseSRootDirectory + ObjectDatabaseFilenames.PRICE_HISTORY))
            {
                PriceHistoryContainer priceHistoryContainer = null;

                //get or create PriceHistoryContainer
                List<PriceHistoryContainer> priceHistoryContainers = database.Query<PriceHistoryContainer>().ToList();

                //PriceHistoryContainer doesnt exist so create it
                if (priceHistoryContainers == null || priceHistoryContainers.Count() == 0)
                {
                    priceHistoryContainer = new PriceHistoryContainer();                    
                }
                else
                {
                    //should only be 1 price history container
                    priceHistoryContainer = priceHistoryContainers[0];
                }

                database.Activate(priceHistoryContainer.Symbols, 2);

                _Log.InfoFormat("Loaded. Contains {0} symbols", priceHistoryContainer.Symbols.Count);

                bool dirtySymbolsList = false;
                Symbol match = null;

                foreach (var downloadedSymbol in downloadedSymbols)
                {
                    match = priceHistoryContainer.Symbols.Find(s =>
                        s.Name == downloadedSymbol.Name &&
                        s.Exchange == downloadedSymbol.Exchange);

                    if (match == null)
                    {
                        _Log.InfoFormat("Adding new symbol: {0} ({1})", downloadedSymbol.Name,
                            Enum.GetName(typeof(Exchange), downloadedSymbol.Exchange));

                        priceHistoryContainer.Symbols.Add(downloadedSymbol);
                        
                        dirtySymbolsList = true;
                    }
                }
                
                if (dirtySymbolsList) database.Store(priceHistoryContainer.Symbols);
            }

            #endregion            

            #region 2. price history
            
            _Log.InfoFormat("Updating price histories");

            IHistoricPricesExtractor _YahooricesExtractor = new YahooHistoricPriceExtractor();                     
            DateTime fromDate = DateTime.MinValue;

            using (IObjectContainer database = Db4oEmbedded.OpenFile(GeCreateIEmbeddedConfiguration(),
                ApplicationConfiguation.ObjectDatabaseSRootDirectory + ObjectDatabaseFilenames.PRICE_HISTORY))
            {
                PriceHistoryContainer priceHistoryContainer = null;

                //get or create PriceHistoryContainer
                List<PriceHistoryContainer> priceHistoryContainers = database.Query<PriceHistoryContainer>().ToList();

                //PriceHistoryContainer doesnt exist so create it
                if (priceHistoryContainers == null || priceHistoryContainers.Count() == 0)
                {
                    priceHistoryContainer = new PriceHistoryContainer();
                }
                else
                {
                    //should only be 1 price history container
                    priceHistoryContainer = priceHistoryContainers[0];                    
                }

                //get symbol list into memory
                database.Activate(priceHistoryContainer.Symbols, 2);
                database.Activate(priceHistoryContainer.LastPriceDate, 2);

                _Log.InfoFormat("Loaded. Contains {0} symbols", priceHistoryContainer.Symbols.Count);
                
                List<DailyPrice> dailyPriceTransporter = new List<DailyPrice>();
                for (int i = 0; i < 10000; i++)
                {
                    dailyPriceTransporter.Add(new DailyPrice());
                }

                Symbol symbol = null;
                DateTime lastPriceDate = priceHistoryContainer.LastPriceDate;

                for (int i = 0; i < priceHistoryContainer.Symbols.Count; i++)
                {
                    symbol = priceHistoryContainer.Symbols[i];

                    //reset the transporter
                    dailyPriceTransporter.ForEach(dp => dp.IsSet = false);

                    _Log.InfoFormat("Updating {0} price history", symbol.Name);

                    //now activate symbols prices list
                    database.Activate(symbol.Prices, 2);                    

                    if (symbol.Prices.Count == 0)
                    {
                        //2 a. has no prices then get entire history (insert)
                        _Log.InfoFormat("Has no prices");

                        fromDate = DateTime.Today.AddYears(-30);
                    }
                    else
                    {
                        //2 b. has prices then get from last price date (update)                       
                        fromDate = symbol.Prices.First().PriceDate.Date.AddDays(1);

                        _Log.InfoFormat("Last price date was {0}", fromDate);
                    }

                    if (fromDate < DateTime.Today)
                    {
                        _Log.InfoFormat("Getting prices");

                        //load the new prices into the transporter
                        _YahooricesExtractor.LoadHistoricPrices(fromDate, DateTime.Today, symbol.Name, dailyPriceTransporter);

                        //add new prices
                        symbol.Prices.InsertRange(0, dailyPriceTransporter.Where(dp => dp.IsSet));                        

                        database.Store(symbol.Prices);                        
                    }                    
                      
                    //clean up references otherwise flooded with prices                  
                    symbol.Prices.Clear();
                    symbol.Prices = null;
                    database.Deactivate(symbol,5);
                    GC.Collect();

                    _Log.InfoFormat("{0:0.0%} completed", (double)i / priceHistoryContainer.Symbols.Count); 
                }                
            }
            
            #endregion
        }

        private IEmbeddedConfiguration GeCreateIEmbeddedConfiguration()
        {
            IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
            config.Common.ActivationDepth = 1;
            config.File.BlockSize = 8;
            //config.Common.ObjectClass(typeof(PriceHistoryContainer)).CascadeOnUpdate(true);
            //config.Common.ObjectClass(typeof(DailyPrice)).CascadeOnUpdate(true);
            //config.Common.ObjectClass(typeof(Symbol)).CascadeOnUpdate(true);

            return config;
        }

        public void Defrag()
        {
            IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();            
            config.Common.Add(new TransparentPersistenceSupport());
            config.File.BlockSize = 8;
            DefragmentConfig defragCfg = new DefragmentConfig(ApplicationConfiguation.ObjectDatabaseSRootDirectory + 
                ObjectDatabaseFilenames.PRICE_HISTORY);
            defragCfg.Db4oConfig(config);
            Defragment.Defrag(defragCfg);
        }

        private PriceHistoryContainer LoadPriceHistoryContainer()
        {
            PriceHistoryContainer priceHistoryContainer = null;

            IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
            config.Common.ObjectClass(typeof(PriceHistoryContainer)).CascadeOnUpdate(true);
            config.Common.ObjectClass(typeof(Symbol)).CascadeOnUpdate(true);

            using (IObjectContainer database = Db4oEmbedded.OpenFile(config,
                ApplicationConfiguation.ObjectDatabaseSRootDirectory + ObjectDatabaseFilenames.PRICE_HISTORY))
            {
                //get or create PriceHistoryContainer
                List<PriceHistoryContainer> priceHistoryContainers = database.Query<PriceHistoryContainer>().ToList();

                //PriceHistoryContainer doesnt exist so create it
                if (priceHistoryContainers == null || priceHistoryContainers.Count() == 0)
                {
                    priceHistoryContainer = new PriceHistoryContainer();
                }
                else
                {
                    //should only be 1 price history container
                    priceHistoryContainer = priceHistoryContainers[0];
                }
            }

            return priceHistoryContainer;
        }

        public void SavePriceHistoryContainer(PriceHistoryContainer priceHistoryContainer)
        {
            IEmbeddedConfiguration config = Db4oEmbedded.NewConfiguration();
            config.Common.ObjectClass(typeof(PriceHistoryContainer)).CascadeOnUpdate(true);
            config.Common.ObjectClass(typeof(Symbol)).CascadeOnUpdate(true);

            using (IObjectContainer database = Db4oEmbedded.OpenFile(config,
                ApplicationConfiguation.ObjectDatabaseSRootDirectory + ObjectDatabaseFilenames.PRICE_HISTORY))
            {
                database.Store(priceHistoryContainer);
            }
        }
    }
}
